
#ifndef __DEBUG_H
#define __DEBUG_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <apr_time.h>

#ifdef VALGRIND_HACKS
#include <valgrind/valgrind.h>
#endif

#ifdef __WIN32__
#define strtok_r strtok_s
#define va_copy(d, s) ((d) = (s))
#endif

#ifndef __WIN32__
#define errprint(k, ...) magical_errprint_moredetail(__PRETTY_FUNCTION__, __LINE__, k, ## __VA_ARGS__)
#define errprintpd(k, p, l, ...) magical_errprint_moredetail(p, l, k, ## __VA_ARGS__)
#else
#define errprint(k, ...) magical_errprint_moredetail(__FILE__, __LINE__, k, ## __VA_ARGS__)
#define errprintpd(k, p, l, ...) magical_errprint_moredetail(p, l, k, ## __VA_ARGS__)
#endif

#ifdef __GNUG__
#ifndef SWIG
#define PRINTF_LIKE(thformat, thvarargs) __attribute__((format(printf,thformat,thvarargs)))
#else
#define PRINTF_LIKE(thformat, thvarargs)
#endif // SWIG
#else
#define PRINTF_LIKE(thformat, thvarargs)
#endif //__GNUG__

#include "acquire_config.h"

extern char *globalplace_execname;
extern char *globalplace_logname;

static void magical_errprint(const char *pretty, const char *k, ...) PRINTF_LIKE(2, 3);
static void magical_errprint_moredetail(const char *pretty, int line, const char *k, ...) PRINTF_LIKE(3, 4);

/** Test to see if the log file with the given name in this directory is writable.
 * 
 * @param directory The directory in which to try opening a log file for writing.
 * 
 * @param argv0_noslash The argv0 of the program to use as the log file name.
 * 
 * @return The name of a file which can be successfully written to, or NULL. */
static char *testLogWritable(const char *directory, const char *argv0_noslash)
{
   if (directory == NULL) return NULL;
   char *file_to_open = (char *)malloc(sizeof(char)*(1 + strlen(directory) + strlen("/") + strlen(argv0_noslash) + strlen(".log")));
   sprintf(file_to_open, "%s/%s.log", directory, argv0_noslash);
   FILE *FP = fopen(file_to_open, "w");
   if (FP)
   {
      fclose(FP);
      return file_to_open;
   }
   free(file_to_open);
   return NULL;
}

/** Test all the possible environment variables for one with a valid
 * temporary directory in it. Try writing a log file to it, and if
 * successful, return the name of the file that is writable.
 * 
 * @param argv0_noslash The argv0 of the program to use as the log file name.
 * 
 * @return The name of a file which can be successfully written to, or NULL. */
static char *findDirectoryForLog(const char *argv0_noslash)
{
   char *tryLogPath = NULL;
   tryLogPath = testLogWritable("/tmp", argv0_noslash);
   if (tryLogPath != NULL) return tryLogPath;
   tryLogPath = testLogWritable(getenv("TMPDIR"), argv0_noslash);
   if (tryLogPath != NULL) return tryLogPath;
   tryLogPath = testLogWritable(getenv("TEMPDIR"), argv0_noslash);
   if (tryLogPath != NULL) return tryLogPath;
   tryLogPath = testLogWritable(getenv("TMP"), argv0_noslash);
   if (tryLogPath != NULL) return tryLogPath;
   tryLogPath = testLogWritable(getenv("TEMP"), argv0_noslash);
   if (tryLogPath != NULL) return tryLogPath;
   return NULL;
}

/** Put argv0 and a proper log file name into a good global variable.
 * 
 * @param argv0 The argv0 of the program, to use to name logs and
 * denote error messages. */
static void putARGV0ToGlobalPlace(const char *argv0)
{
   // Assuming we have a globalplace_execname and a globalplace_logname
   char *filtered_argv0 = strdup(argv0);
   char *last_slash = filtered_argv0;
   char *last_slash2 = filtered_argv0;
   while ((last_slash2 = strstr(&(last_slash[1]), "/")) != NULL) last_slash = last_slash2;
   if (last_slash != filtered_argv0)
   {
      char *temp_place = strdup(&(last_slash[1]));
      free(filtered_argv0);
      filtered_argv0 = temp_place;
   } else
   {
      // Attempt Windows path removal.
      last_slash = filtered_argv0;
      last_slash2 = filtered_argv0;
      while ((last_slash2 = strstr(&(last_slash[1]), "\\")) != NULL) last_slash = last_slash2;
      if (last_slash != filtered_argv0)
      {
         char *temp_place = strdup(&(last_slash[1]));
         free(filtered_argv0);
         filtered_argv0 = temp_place;
      }
   }
   globalplace_execname = filtered_argv0;
   globalplace_logname = findDirectoryForLog(globalplace_execname);
   if (globalplace_logname == NULL)
   {
      globalplace_logname = (char *)malloc(sizeof(char)*(1 + strlen(globalplace_execname) + strlen("./") + strlen(".log")));
      sprintf(globalplace_logname, "./%s.log", globalplace_execname);
   }
}

/** Magical error printing function, takes in a pretty function name
 * and attaches the error to the function it came from (must be called
 * through the errprint macro). */
static void magical_errprint(const char *pretty, const char *k, ...)
{
#ifdef ACQUIRE_LOG_FILE
   fprintf(stderr, ".");
   FILE *filepntr = fopen(globalplace_logname, "a");
#else
   FILE *filepntr = stderr;
#endif
   char datevalue[APR_RFC822_DATE_LEN + 1];
   if (apr_rfc822_date(datevalue, apr_time_now()))
      datevalue[0] = '\0';
   va_list fmtargs;
   va_start(fmtargs, k);
   char *__errmsg_buffer = (char *)malloc(sizeof(char)*(APR_RFC822_DATE_LEN+3+strlen(pretty)+strlen(k)+1));
   if (__errmsg_buffer == NULL) fprintf(filepntr, "%s : %s - malloc unavailable\n", datevalue, pretty);
   strcpy(__errmsg_buffer, pretty);
   int __charno = 0;
   while (__errmsg_buffer[__charno++] != ' ');
   int __charno_2 = __charno;
   while (__errmsg_buffer[++__charno_2] != '(');
   __errmsg_buffer[__charno_2] = '\0';
   fprintf(filepntr, "%s : %s: ", datevalue, &(__errmsg_buffer[__charno]));
   vfprintf(filepntr, k, fmtargs);
   va_end(fmtargs);
   fprintf(filepntr, "\n");
   free(__errmsg_buffer);
#ifdef ACQUIRE_LOG_FILE
   fclose(filepntr);
#endif
}

/** Even more magical error printing function, takes in a pretty
 * function name and attaches the error to the function and line
 * number that it came from (must be called through the errprint
 * macro). This version also generates a useful backtrace under
 * Valgrind. */
static void magical_errprint_moredetail(const char *pretty, int line, const char *k, ...)
{
#ifdef ACQUIRE_LOG_FILE
   fprintf(stderr, ".");
   FILE *filepntr = fopen(globalplace_logname, "a");
#else
   FILE *filepntr = stderr;
#endif
   char datevalue[APR_RFC822_DATE_LEN + 1];
   if (apr_rfc822_date(datevalue, apr_time_now()))
      datevalue[0] = '\0';
   va_list fmtargs;
   va_start(fmtargs, k);
   char *__errmsg_buffer = (char *)malloc(sizeof(char)*(APR_RFC822_DATE_LEN+3+strlen(pretty)+32+strlen(k)+1));
   if (__errmsg_buffer == NULL) fprintf(filepntr, "%s : %s:%d - malloc unavailable\n", datevalue, pretty, line);
   strcpy(__errmsg_buffer, pretty);
   int lastbrack = 0, count = 0;
   while (__errmsg_buffer[count] != '\0')
   {
      if (__errmsg_buffer[count] == '(') lastbrack = count;
      count++;
   }
   __errmsg_buffer[lastbrack] = '\0';
   char *remaining = NULL;
   char *token = strtok_r(__errmsg_buffer, " ", &remaining);
   char *lasttoken = NULL;
   while (token != NULL)
   {
      lasttoken = token;
      token = strtok_r(NULL, " ", &remaining);
   }
   if (lasttoken)
   {
      int tlen = strlen(lasttoken);
      memmove(__errmsg_buffer, lasttoken, sizeof(char)*tlen);
      __errmsg_buffer[tlen] = '\0';
   } else strcpy(__errmsg_buffer, pretty);
   fprintf(filepntr, "%s : %s:%d: ", datevalue, __errmsg_buffer, line);
   vfprintf(filepntr, k, fmtargs);
   va_end(fmtargs);
   fprintf(filepntr, "\n");
#ifdef VALGRIND_HACKS
   VALGRIND_PRINTF_BACKTRACE("");
#endif
   free(__errmsg_buffer);
#ifdef ACQUIRE_LOG_FILE
   fclose(filepntr);
#endif
}

#endif
